Лабораторная работа 10 "Подсистема прерывания"

Данная лабораторная работа посвящена изучению систем прерывания в компьютерах и их использованию для обработки программных и аппаратных событий. В процессе работы вы познакомитесь с основными понятиями и принципами работы систем прерывания, а также со средствами программной обработки прерываний.

Цель

  1. Разработать модуль контроллера прерываний.
  2. Разработать модуль контроллера регистров статуса и контроля (CSR-контроллер).

Ход выполнения

  1. Изучение теории по прерываниям и исключениям в архитектуре RISC-V, включая работу с регистрами статуса и контроля (CSR) и механизмы реализации прерываний.
  2. Реализация схемы обработки прерывания для устройства на основе RISC-V
  3. Реализация схемы управления регистрами статуса и контроля.

Теоретическая часть

Прерывания/Исключения

С компьютером постоянно происходят события, на которые он должен реагировать, запуская соответствующие подпрограммы. Например, при движении мышки нужно перерисовать ее курсор на новом месте или нужно среагировать на подключение флешки и т.п. Возможность запускать нужные подпрограммы в ответ на различные события, возникающие внутри или снаружи компьютера, существенно расширяют его возможности. События, требующие внимания процессора называются прерываниями (interrupt). Происходящие события формируют запрос на прерывание процессору.

С.А. Орлов, Б.Я. Цилькер в учебнике "Организация ЭВМ и систем" дают следующее определение системе прерывания:

Система прерывания – это совокупность программно-аппаратных средств, позволяющая процессору (при получении соответствующего запроса) на время прервать выполнение текущей программы, передать управление программе обслуживания поступившего запроса, по завершению которой и продолжить прерванную программу с того места, где она была остановлена[1, стр. 155].

Прерывания делятся на маски́руемые — которые при желании можно игнорировать (на которые можно наложить битовую маску, отсюда ударение на второй слог), и немаски́руемые — которые игнорировать нельзя (например сбой генератора тактового синхроимпульса в микроконтроллерах семейства PIC24FJ512GU410[2, стр. 130]). Прерывание похоже на незапланированный вызов функции, вследствие события в аппаратном обеспечении. Программа (функция), запускаемая в ответ на прерывание, называется обработчиком прерывания.

События могут быть не только аппаратными, но и программными – синхронными. Такие события называются исключениями (exception). Программа может столкнуться с состоянием ошибки, вызванным программным обеспечением, таким как неопределенная инструкция, неподдерживаемая данным процессором, в таком случаях говорят, что возникло исключение. К исключениям также относятся сброс, деление на ноль, переполнение и попытки считывания из несуществующей памяти.

Важно понимать, что ни прерывание, ни исключение не являются обязательно чем-то плохим. И то и другое — это всего лишь события. Например, с помощью исключений может осуществляться системные вызовы и передача управления отладчику программы.

Как и любой другой вызов функции, при возникновении прерывания или исключения необходимо сохранить адрес возврата, перейти к программе обработчика, выполнить свою работу, восстановить контекст (не оставить никаких следов работы обработчика прерывания) и вернуться к программе, которую прервали.

Благодаря исключениям можно реализовать имитацию наличия каких-то аппаратных блоков программными средствами. Например, при отсутствии аппаратного умножителя, можно написать программу обработчика исключения неподдерживаемой инструкции умножения, реализующую алгоритм умножения через сложение и сдвиг. Тогда, каждый раз, когда в программе будет попадаться инструкция умножения, будет возникать исключение, приводящее к запуску обработчика, перемножающего числа и размещающего результат в нужные ячейки памяти. После выполнения обработчика управление возвращается программе, которая даже не поймет, что что-то произошло и умножитель «ненастоящий».


На протяжении многих лет, концепция понятия "прерывание" постоянно расширялась. Семейство процессоров 80x86 внесло ещё большую путаницу введя инструкцию int (программное прерывание). Многие производители используют такие термины как: исключение (exception), ошибка (fault), отказ (abort), ловушка (trap) и прерывание (interrupt), чтобы описать явление, которому посвящена данная лабораторная работа. К несчастью, не существует какого-то чёткого соглашения насчёт этих названий. Разные авторы по-разному приспосабливают эти термины для своего повествования[3, стр. 995]. Для того, чтобы постараться избежать путаницы, в данной лабораторной работе мы будем использовать три термина, которые введены в спецификации архитектуры RISC-V[4, стр. 10], однако имейте в виду, что за пределами этой методички и спецификации RISC-V в эти термины могут вкладывать другие смыслы.

Сперва озвучим выдержку из спецификации, а потом дадим этим терминам обывательские определения.

  1. Под исключением будут подразумеваться нетипичные условия, произошедшие во время исполнения программы, связанные с инструкцией в текущем харте (hart, сокращение от hardware thread — аппаратном потоке).
  2. Под прерыванием будут подразумеваться внешние асинхронные события, которые могут стать причиной непредвиденной передачи управления внутри текущего харта.
  3. Под перехватом (вариант глагольного использования слова trap, которое обычно переводят как "ловушка") будет подразумеваться передача управления обработчику перехватов (trap handler), вызванная либо прерыванием, либо исключением.

Иными словами, прерываниями мы будем называть исключительно аппаратные (внешние, асинхронные) события, которые могут привести к перехвату (передаче управления обработчику). Под исключениями мы будем подразумевать исключительно программные (являющиеся следствием какой-то инструкции, синхронные) события, которые могут привести к перехвату.

Соответственно перехватом будет называться обобщение этих двух терминов.

Прерывания и исключения — это события (причины). Перехват — это действие (следствие).


Современные процессоры, предусматривающие запуск операционной системы, обладают несколькими уровнями привилегий выполнения инструкций. Это значит, что существует специальный регистр, определяющий режим, в котором в данный момент находится вычислительная машина. Наличие определенного значения в этом регистре устанавливает определенные ограничения для выполняемой в данный момент программы. В архитектуре RISC-V выделяется 4 режима работы, в порядке убывания возможностей и увеличения ограничений:

  1. машинный (machine mode), в котором можно всё;
  2. гипервизора (hypervisor mode), который поддерживает виртуализацию машин, то есть эмуляцию нескольких машин (потенциально с несколькими операционными системами), работающих на одной физической машине;
  3. привилегированный (supervisor mode), для операционных систем, с возможностью управления ресурсами;
  4. пользовательский (user mode), для прикладных программ, использующих только те ресурсы, которые определила операционная система.

../../.pic/Labs/lab_10_irq/fig_01.png

Рисунок 1. Распределение привилегий по уровням абстракций программного обеспечения.

Переключение между этими режимами происходит с помощью исключения, называемого системный вызов, и который происходит при выполнении специальной инструкции. Для RISC-V такой инструкцией является ecall. Это похоже на вызов подпрограммы, но при системном вызове изменяется режим работы и управление передается операционной системе, которая, по коду в инструкции вызова определяет, что от нее хотят. Например, операционная система может предоставить данные с диска, так как запускаемая программа не имеет никакого представления о том, на какой машине ее запустили, или что используется какая-то конкретная файловая система.

Системы прерываний имеет ряд характеристик, которые варьируются в зависимости от их реализации. Все системы можно условно разбить на две категории: обзорные (прямые) и векторные.

В обзорных системах прерывания любой перехват приводит к запуску одного и того же обработчика. Внутри такого обработчика перехвата определяется причина его возникновения (как правило — это число в специальном регистре), и уже в зависимости от причины запускается нужная подпрограмма. Обзорные системы аппаратно проще векторных, но требуют больше рутины и времени на обработку. Именно такая система прерываний будет реализована в данной лабораторной работе.

В векторных системах прерывания разные события приводят к запуску на исполнение разных программ обработчиков. Адрес начала обработчика перехвата называется вектором прерывания. В векторных системах прерывания выделяется фрагмент памяти, в котором хранятся адреса переходов на начало каждого из обработчиков. Такой участок памяти называется таблицей векторов прерываний (Interrupt Vector Table, IVT).

В самом простом случае система прерывания позволяет обрабатывать только одно прерывание за раз (именно такую систему мы и будет делать в рамках данной лабораторной работы). Существуют реализации позволяющие во время обработки прерывания «отвлекаться» на другие события. В таких системах используется система приоритетов, чтобы прерывание с более низким приоритетом не прерывало более приоритетное.

Регистры Статуса и Управления (Control and Status Registers)

Для поддержания работы операционной системы, виртуализации, системы прерывания и тому подобное, в архитектуре RISC-V предусмотрено использование группы регистров, под общим названием Control and Status Registers (CSR), обеспечивающих управление элементами процессора и доступ к статусной информации о системе. С помощью этих регистров реализуются привилегированные режимы работы процессора, хранение указателей на различные программные стеки, статус различных подсистем, регистры для обеспечения работы прерываний и многое другое.

Все регистры имеют уникальные 12-битные адреса, а их роли определены в спецификации на архитектуру RISC-V. В Таблице 1 приводится фрагмент спецификации привилегированной архитектуры[5, стр. 10], иллюстрирующая некоторые из регистров. В левом столбце указан 12-битный адрес. Далее указывается в каком режиме, что можно делать с этим регистром. После идет название, а в правом столбике описание.

В этой таблице можно увидеть регистры для сохранения адреса возврата из прерывания, адрес вектора прерывания, регистры причины (cause), регистры настройки безопасности и защиты памяти. И это далеко не полный список регистров, предоставляемых стандартом (который помимо прочего, оставляет место в адресном пространстве для ваших собственных регистров).

../../.pic/Labs/lab_10_irq/tab_01.png

Таблица 1. Регистры контроля и состояния машинного (наивысшего) уровня привилегий.

Для работы с CS-регистрами используются специальные инструкции SYSTEM (1110011) I-типа, хранящие в 12-битном поле imm адрес регистра, к которому будет осуществлен доступ и адреса в регистровом файле откуда будет считан или куда будет записан один из CS-регистров. Вы уже добавляли поддержку этих инструкций во время выполнения лабораторной работы №5 "Основной дешифратор".

opcodefunc3ТипИнструкцияОписаниеОперация
1110011000ImretВозврат из прерыванияPC = mepc
1110011001Icsrrw rd, csr, rs1Чтение и Запись CSRrd = csr, csr = rs1
1110011010Icsrrs rd, csr, rs1Чтение и Установка бит CSRrd = csr, csr = csr | rs1
1110011011Icsrrc rd, csr, rs1Чтение и Очистка бит CSRrd = csr, csr = csr & ~rs1
1110011101Icsrrwi rd, csr, rs1Чтение и Запись CSRrd = csr, csr = imm
1110011110Icsrrsi rd, csr, rs1Чтение и Установка бит CSRrd = csr, csr = csr | imm
1110011111Icsrrci rd, csr, rs1Чтение и Очистка бит CSRrd = csr, csr = csr & ~imm

Таблица 2. Список инструкций для работы с регистрами контроля и статуса.

Для удобства программирования на языке ассемблера RISC-V существуют псевдоинструкции для работы с CS-регистрами.

ПсевдоинструкцияИнструкция RISC-VОписаниеОперация
csrr rd, csrcsrrs rd, csr, x0Чтение CSRrd = csr
csrw csr, rs1csrrw x0, csr, rs1Запись CSRcsr = rs1

Таблица 3. Псевдоинструкции для работы с регистрами контроля и статуса.

Операция логического ИЛИ нулевого регистра с содержимым CS-регистра не меняет его содержимого, поэтому при использовании инструкции csrr происходит только операция чтения. Подобным образом реализована псевдоинструкция csrw.

Для реализации простейшей системы прерывания на процессоре с архитектурой RISC-V достаточно реализовать 5 CS-регистров, работающих в машинном, самом привилегированном режиме.

АдресУровень привилегийНазваниеОписание
Machine Trap Setup
0x304MRWmieРегистр маски прерываний.
0x305MRWmtvecБазовый адрес обработчика перехвата.
0x340MRWmscratchАдрес верхушки стека обработчика перехвата.
0x341MRWmepcРегистр, хранящий адрес перехваченной инструкции.
0x342MRWmcauseПричина перехвата

Таблица 4. Список регистров, подлежащих реализации в рамках лабораторной работы.

По адресу 0x304 должен располагаться регистр, позволяющий маскировать прерывания. Например, если на 5-ом входе системы прерывания генерируется прерывание, то процессор отреагирует на него только в том случае, если 5-ый бит регистра mie будет равен 1.

Регистр mtvec состоит из двух полей: BASE[31:2] и MODE. Поле BASE хранит старшие 30 бит базового адреса обработчика перехвата (поскольку этот адрес должен быть всегда равен четырем, младшие два бита считаются равными нулю). Поле MODE кодирует тип системы прерывания:

  • MODE == 2'd0 — система прерывания обзорная;
  • MODE == 2'd1 — система прерывания векторная.

../../.pic/Labs/lab_10_irq/fig_02.png

Рисунок 2. Разделение регистра mtvec на поля BASE и MODE

В случае обзорной системы прерывания, любой перехват приводит к загрузке в PC значения базового адреса обработчика перехвата (PC=BASE). В векторной системе прерывания исключения обрабатываются таким же способом, как и в обзорной системе, а вот прерывания обрабатываются путем загрузки в PC суммы базового адреса и учетверенного значения причины прерывания (PC=BASE+4*CAUSE).

В рамках данной лабораторной работы мы будем реализовывать обзорную систему прерываний. Кроме того, поскольку у обзорной системы прерываний MODE==0, что совпадет с тем, что два младших бита базового адреса обработчика перехвата должны быть равны нулю, при перехвате мы можем присваивать программному счетчику значение mtvec без каких-либо преобразований.

Так как обработчик перехвата будет использовать те же регистры, что и прерванная программа, то перед использованием регистрового файла, данные из него необходимо сохранить, разместив их на стеке. Стек для перехвата находится не там же, где программный стек, а адрес начала этого стека хранится в регистре mscratch и по сути является указателем на верхушку стека. Регистр mepc сохраняет адрес инструкции во время которой произошел перехват. Это очень важно понимать, при реализации обработчика исключения — если в нем не перезаписать этот регистр, по возврату из обработчика процессор снова окажется на инструкции, которая вызвала исключение.

То как кодируется причина перехвата в регистре mcause описано в спецификации привилегированной архитектуры[5, стр. 38]:

../../.pic/Labs/lab_10_irq/tab_05.png

Таблица 5. Кодирование причины перехвата в регистре mcause.

Нас интересуют части, выделенные красным. В первую очередь то, как кодируется старший бит регистра mcause. Он зависит от типа причины перехвата (1 в случае прерывания, 0 в случае исключения). Оставшиеся 31 бит регистра отводятся под коды различных причин. Поскольку мы создаем учебный процессор, который не будет использован в реальной жизни, он не будет поддерживать большую часть прерываний/исключений (таких как невыровненный доступ к памяти, таймеры и т.п.). В рамках данного курса мы должны поддерживать исключение по нелегальной инструкции (код 0x02) и должны уметь поддерживать прерывания периферийных устройств (под которые зарезервированы коды начиная с 16-го). В рамках данной лабораторной работы процессор будет поддерживать только один источник прерывания, поэтому для кодирования причины прерывания нам потребуется только первый код из диапазона "Designated for platform use". В случае, если вы захотите расширить количество источников прерываний, вы можете выполнить вспомогательную лабораторную работу №12.

Таким образом: в случае если произошло исключение (в связи с нелегальной инструкцией), значение mcause должно быть 0x00000002. Если произошло прерывание, значение mcause должно быть 0x80000010.

При желании, процессор можно будет улучшить, добавив поддержку большего числа периферийных устройств. В этом случае потребуется только расширить контроллер прерываний.

Когда процессор включается, программа первым делом должна инициализировать все требуемые CS-регистры, в частности:

  • задать маску прерывания mie,
  • задать адрес вектора прерывания mtvec,
  • задать адрес вершины стека прерываний mscratch.

После чего уже можно переходить к исполнению основного потока инструкций.

Реализация прерываний в архитектуре RISC-V

Процессор RISC-V может работать в одном из нескольких режимов выполнения с различными уровнями привилегий. Машинный режим – это самый высокий уровень привилегий; программа, работающая в этом режиме, может получить доступ ко всем регистрам и ячейкам памяти. M-режим является единственным необходимым режимом привилегий и единственным режимом, используемым в процессорах без операционной системы, включая многие встраиваемые системы.

Обработчики прерываний/исключений используют для перехвата четыре специальных регистра управления и состояния (CSR): mtvec, mcause, mepc и mscratch. Регистр базового адреса вектора прерывания mtvec, содержит адрес кода обработчика прерывания. При перехвате процессор:

  • записывает причину перехвата в mcause,
  • сохраняет адрес перехваченной инструкции, в mepc,
  • переходит к обработчику перехвата, загружая в PC адрес, предварительно настроенный в mtvec.

После перехода по адресу в mtvec обработчик считывает регистр mcause, чтобы проверить, что вызвало прерывание или исключение, и реагирует соответствующим образом (например, считывая клавиатуру при аппаратном прерывании).

После выполнения программы-обработчика перехвата, возвращение в программу выполняется командой возврата mret, которая помещает в PC значение регистра mepc. Сохранение PC инструкции при прерывании в mepc аналогично использованию регистра ra для хранения обратного адреса во время инструкции jal. Обработчики прерываний должны использовать программные регистры (x1−x31) для своей работы, поэтому они используют память, на которую указывает mscratch, для хранения и восстановления этих регистров.

Контроллер прерываний – это блок процессора, обеспечивающий взаимодействие с устройствами, запрашивающими прерывания, формирование кода причины прерывания для процессора, маскирование прерываний, а также, в других реализациях, может реагировать на прерывания в соответствии с приоритетом и тому подобное.

Периферийное устройство, которое может генерировать прерывание, подключается к контроллеру прерывания паре проводов: запрос на прерывание (irq_req_i) и прерывание обслужено (irq_ret_o). Предположим, к контроллеру прерываний подключили клавиатуру. Когда на ней нажимают клавишу, код этой клавиши попадает в буферный регистр с дополнительным управляющим битом, выставленным в единицу, который подключен к входу запроса на прерывание. Если прерывание не замаскировано (в нашем процессоре это означает, что нулевой бит регистра mie выставлен в 1), то контроллер прерывания сгенерирует код причины прерывания (в нашем случае — это константа 0x80000010). Кроме этого, контроллер прерывания подаст сигнал irq_o, чтобы устройство управления процессора узнало, что произошло прерывание и разрешило обновить содержимое регистра причины mcause, сохранило адрес прерванной инструкции в mepc и загрузило в PC вектор прерывания mtvec.

Когда будет выполняться инструкция mret, устройство управления подаст сигнал контроллеру прерывания, чтобы тот, в свою очередь, направил его в виде сигнала «прерывание обслужено» для соответствующего устройства. После этого периферийное устройство обязано снять сигнал запроса прерывания хотя бы на один такт. В нашем примере сигнал «прерывание обслужено» может быть подключен непосредственно к сбросу буферного регистра клавиатуры.

Структура разрабатываемых устройств

В рамках лабораторной работы необходимо реализовать поддержку обработки аппаратных прерываний. Для этого необходимо реализовать два модуля: блок управления регистрами контроля и статуса (CSR-контроллер) и контроллер прерываний (Interrupt Controller).

Блок управления регистрами контроля и статуса позволяет добавить особые архитектурные регистры, которые будут использоваться нами при обработке прерываний и исключений.

Контроллер прерываний позволит обрабатывать входящие запросы на прерывания: маски́ровать их, выбирать один запрос из нескольких, а также игнорировать запросы во время обработки текущего прерывания.

../../.pic/Labs/lab_10_irq/fig_03.drawio.svg

Рисунок 3. Место разрабатываемых блоков в структуре процессора.

Пока что вам нужно реализовать только блоки irq controller и control status registers, а не саму схему, приведенную выше.

CSR-контроллер

Рассмотрим один из возможных вариантов организации блока Control and Status Registers. Основная работа по описанию схемы блока состоит в описании мультиплексора и демультиплексора. Мультиплексор подает на выход read_data_o значение регистра, который соответствует пришедшему адресу. В свою же очередь, демультиплексор маршрутизирует сигнал разрешения на запись write_enable_i (en) на тот же регистр.

../../.pic/Labs/lab_10_irq/fig_04.drawio.svg

Рисунок 4. Структурная схема контроллера CS-регистров.

3-битный вход opcode_i определяет операцию, которая будет производиться над содержимым CSR по адресу addr_i.

Для реализации мультиплексора на языке описания аппаратуры SystemVerilog можно воспользоваться конструкцией case внутри блока always_comb. Для реализации демультиплексора также можно использовать case, только если при описании мультиплексора в зависимости от управляющего сигнала на один и тот же выход идут разные входы, то при описании демультиплексора все будет наоборот: в зависимости от управляющего сигнала, один и тот же вход будет идти на разные выходы (например, на разные биты многоразрядной шины enable).

Мультиплексоры, располагаемые на входах регистров mepc и mcause нужны, чтобы при возникновении сигнала прерывания сразу же разрешить обновить значение этих регистров значением pc_i, на котором произошел перехват и кодом причины происходящего сейчас перехвата.

Контроллер прерываний

Рассмотрим один из возможных способов реализации простейшего контроллера прерываний, представленного на рис. 5.

../../.pic/Labs/lab_10_irq/fig_05.drawio.svg

Рисунок 5. Структурная схема контроллера прерываний.

Контроллер состоит из логики:

  • обработки вложенных прерываний, частью которой являются регистры отслеживания обработки прерывания и исключения (irq_h и exc_h соответственно),
  • установки и сброса этих регистров (которая вместе с этими регистрами заключена в штрихованные прямоугольники),
  • приоритета исключений над прерываниями,
  • маскирования запросов на прерывание.

Регистры отслеживания обработки прерывания и исключения нужны для того, чтобы мы могли понимать, что в данный момент процессор уже выполняет обработку прерывания / исключения. В такие моменты (если любой из регистров exc_h/irq_h содержит значение 1) все последующие запросы на прерывание игнорируются. За это отвечают вентили И и ИЛИ-НЕ в правом верхнем углу схемы.

Однако возможна ситуация возникновения исключения во время обработки прерывания — в этом случае, оба регистра будут хранить значение 1. В момент возврата из обработчика, придет сигнал mret_i, который в первую очередь сбросит регистр exc_h и только если тот равен нулю, сбросит регистр irq_h.

Исключение во время обработки исключения не поддерживается данной микроархитектурой и приведет к неопределенному поведению. Поэтому код обработчика исключений должен быть написан с особым вниманием.

Логика установки и сброса работает следующим образом:

  • если сигнал, обозначенный в прямоугольнике как reset равен единице, в регистр будет записано значение 0;
  • если сигнал, обозначенный в прямоугольнике как set равен единице, в регистр будет записано значение 1;
  • в остальных случах, регистр сохраняет свое значение.

Обратите внимание, что логика установки и сброса регистров дает приоритет сбросу, хотя сигнал сброса никогда не придет одновременно с сигналом установки (поскольку инструкция mret не генерирует исключение, сигнал mret_i никогда не придет одновременно с сигналом exception_i, а логика приоритета исключений над прерываниями не даст сигналу mret распространиться до регистра irq_h одновременно с формированием сигнала irq_o).

Логика приоритета исключений над прерываниями заключается в том, что сигнал exception_i является частью логики обработки вложенных прерываний. Пройдя через два логических ИЛИ и последующий инвертор, этот сигнал обнулит запрос на прерывание на логическом И в правом верхнем углу.

Логика маскирования запросов на прерывания заключается в простейшем И между запросом на прерывания (irq_req_i) и сигналом разрешения прерывания (mie_i).

Пример обработки перехвата

Ниже представлен пример программы и обработчика перехватов. Программа начинается с инициализации начальных значений регистров управления, указателя на верхушку стека и глобальную область данных, после чего уходит в бесконечный цикл ничего не делая, до тех пор, пока не произойдет перехват.

Алгоритм работы обработчика перехвата (trap handler) выглядит следующим образом:

  1. сохраняется содержимое регистрового файла на стек;
  2. проверяется регистр причины чтобы запустить необходимую подпрограмму;
  3. происходит вызов необходимой подпрограммы;
  4. после возврата происходит восстановление содержимого регистрового файла;
  5. затем происходит возврат управления прерванной программе.

Если бы система прерывания была векторной, то рутина со считыванием кода причины отсутствовала.

_start:
# Инициализируем начальные значения регистров
00: li x2, 0x00003FFC       # устанавливаем указатель на верхушку стека
04:                         # данная псевдоинструкция будет разбита на две
                            # инструкции: lui и addi

08: li x3, 0x00000000       # устанавливаем указатель на глобальные данные

0C: li x5, 0x00000001       # подготавливаем маску прерывания единственного
                            # (нулевого) входа
10: csrw mie, x5            # загружаем маску в регистр маски

14: la x5, trap_handler     # псевдоинструкция la аналогично li загружает число,
18:                         # только в случае la — это число является адресом
                            # указанного места (адреса обработчика перехвата)
                            # данная псевдоинструкция будет разбита на две
                            # инструкции: lui и addi

1С: csrw mtvec, x5          # устанавливаем вектор прерывания

20: li x5, 0x00001FFC       # готовим адрес верхушки стека прерывания
24:                         # данная псевдоинструкция будет разбита на две
                            # инструкции: lui и addi

28: csrw mscratch, x5       # загружаем указатель на верхушку стека прерывания

2С: li x5, 1                # начальное значение глобальной переменной
30: sw x5, 0(x3)            # загружаем переменную в память

34: li x6, 0                # начальное значение, чтобы в симуляции не было xxx
38: li x7, 0                # начальное значение, чтобы в симуляции не было xxx

# Вызов ecall исключительно из хулиганских соображений, поскольку в данной
# микроархитектурной реализации это приведет к появлению illegal_instr и
# последующей обработке исключения
3С: ecall

# Вызов функции main
main:
40: beq x0, x0, main        # бесконечный цикл, аналогичный while (1);

# ОБРАБОТЧИК ПЕРЕХВАТА
# Без стороннего вмешательства процессор никогда не перейдет к инструкциям ниже,
# однако в случае прерывания в программный счетчик будет загружен адрес первой
# нижележащей инструкции.

# Сохраняем используемые регистры на стек
trap_handler:
44: csrrw x5, mscratch, x5  # меняем местами mscratch и x5
48: sw x6, 0(x5)            # сохраняем x6 на стек mscratch
4С: sw x7, 4(x5)            # сохраняем x7 на стек mscratch

# Проверяем произошло ли прерывание
50: csrr x6, mcause         # x6 = mcause
54: li x7, 0x80000010       # загружаем в x7 код того, что произошло прерывание
58:                         # данная псевдоинструкция будет разбита на две
                            # инструкции: lui и addi
5C: bne x6, x7, exc_handler # если коды не совпадают, переходим к проверке
                            # на исключение
# Обработчик прерывания
60: lw x7, 0(x3)            # загружаем переменную из памяти
64: addi x7, x7, 3          # прибавляем к значению 3
68: sw x7, 0(x3)            # возвращаем переменную в память
6C: j done                  # идем возвращать регистры и на выход

exc_handler:                # Проверяем произошло ли исключение
70: li x7, 0x0000002        # загружаем в x7 код того, что произошло исключение
74: bne x6, x7, done        # если это не оно, то выходим

# Обработчик исключения
78: csrr x6, mepc           # Узнаем значение PC (адреса инструкции,
                            # вызвавшей исключение)
7C: lw x7, 0x0(x6)          # Загружаем эту инструкцию в регистр x7.
                            # В текущей микроархитектурной реализации это
                            # невозможно, т.к. память инструкций отделена от
                            # памяти данных и не участвует в выполнении
                            # операций load / store.
                            # Другой способ узнать об инструкции, приведшей
                            # к исключению — добавить поддержку статусного
                            # регистра mtval, в который при исключении
                            # может быть записана текущая инструкция.
                            # Теоретически мы могли бы после этого
                            # сделать что-то, в зависимости от этой инструкции.
                            # Например, если это операция умножения — вызвать
                            # подпрограмму умножения.

80: addi x6, x6, 4          # Увеличиваем значение PC на 4, чтобы после
                            # возврата не попасть на инструкцию, вызвавшую
                            # исключение.
84: csrw mepc, x6           # Записываем обновленное значение PC в регистр mepc
88: j done                  # идем восстанавливать регистры со стека и на выход

# Возвращаем регистры на места и выходим
done:
8C: lw x6, 0(x5)            # возвращаем x6 со стека
90: lw x7, 4(x5)            # возвращаем x7 со стека
94: csrrw x5, mscratch, x5  # меняем обратно местами x5 и mscratch
98: mret                    # возвращаем управление программе (pc = mepc)
                            # что означает возврат в бесконечный цикл

Задание

  1. Описать на языке SystemVerilog модуль контроллера регистров статуса и контроля (CSR-контроллер) со следующим прототипом:
module csr_controller(

  input  logic        clk_i,
  input  logic        rst_i,
  input  logic        trap_i,

  input  logic [ 2:0] opcode_i,

  input  logic [11:0] addr_i,
  input  logic [31:0] pc_i,
  input  logic [31:0] mcause_i,
  input  logic [31:0] rs1_data_i,
  input  logic [31:0] imm_data_i,
  input  logic        write_enable_i,

  output logic [31:0] read_data_o,
  output logic [31:0] mie_o,
  output logic [31:0] mepc_o,
  output logic [31:0] mtvec_o
);

import csr_pkg::*;

endmodule
  1. Описать на языке SystemVerilog модуль контроллера прерываний со следующим прототипом:
module interrupt_controller(
  input  logic        clk_i,
  input  logic        rst_i,
  input  logic        exception_i,
  input  logic        irq_req_i,
  input  logic        mie_i,
  input  logic        mret_i,

  output logic        irq_ret_o,
  output logic [31:0] irq_cause_o,
  output logic        irq_o
);

endmodule

Порядок выполнения задания

  1. Внимательно ознакомьтесь с описанием модуля csr_controller и его структурной схемой. В случае возникновения вопросов, проконсультируйтесь с преподавателем.
  2. Реализуйте модуль csr_controller. Для этого:
    1. В Design Sources проекта с предыдущих лаб, создайте SystemVerilog-файл csr_controller.sv.
    2. Опишите в нем модуль csr_controller с таким же именем и портами, как указано в задании.
    3. Обратите внимание на наличие импорта пакета csr_pkg, данный пакет содержит адреса используемых регистров контроля и статуса, которыми будет удобно пользоваться при реализации модуля.
  3. После описания модуля, его необходимо проверить с помощью тестового окружения.
    1. Тестовое окружение находится здесь.
    2. Для запуска симуляции воспользуйтесь этой инструкцией.
    3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (tb_csr).
    4. Во время симуляции, вы должны прожать "Run All" и убедиться, что в логе есть сообщение о завершении теста!
  4. Внимательно ознакомьтесь с описанием функционального поведения сигналов interrupt_controller, а также его структурной схемой. В случае возникновения вопросов, проконсультируйтесь с преподавателем.
  5. Реализуйте модуль interrupt_controller. Для этого:
    1. В Design Sources проекта с предыдущих лаб, создайте SystemVerilog-файл interrupt_controller.sv.
    2. Опишите в нем модуль interrupt_controller с таким же именем и портами, как указано в задании.
  6. После описания модуля, его необходимо проверить с помощью тестового окружения.
    1. Тестовое окружение находится здесь.
    2. Для запуска симуляции воспользуйтесь этой инструкцией.
    3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (tb_irq).
    4. Во время симуляции, вы должны прожать "Run All" и убедиться, что в логе есть сообщение о завершении теста!

Список использованной литературы

  1. С.А. Орлов, Б.Я. Цилькер / Организация ЭВМ и систем: Учебник для вузов. 2-е изд. / СПб.: Питер, 2011.
  2. PIC24FJ512GU410 Family Data Sheet
  3. The Art of Assembly Language
  4. The RISC-V Instruction Set Manual Volume I: Unprivileged ISA
  5. The RISC-V Instruction Set Manual Volume II: Privileged Architecture